﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Whe;
using WOS;
using WOS.Common;

namespace WOS.Download
{
    internal sealed class DownloadItem : Item, IClassifiable
    {
        #region Private Fields
        private DateTime stamp;
        private string folder;
        private string location;
        private long progress;
        private DownloadPriority priority;
        private DownloadStatus status;
        private ItemOrigin origin;
        private ItemOutput output;
        private bool? isProtected;
        private bool? isSuspected;

        private List<Guid> providers;
        private List<ItemResource> sources;
        private Dictionary<ItemResource, IList<ITaskInfo>> tasks;
        #endregion
        #region Constructors
        public DownloadItem(Guid id, string name, ItemOrigin origin)
            : base(id, name)
        {
            this.stamp = DateTime.Now;
            this.folder = String.Empty;
            this.progress = 0;
            this.priority = DownloadPriority.Normal;
            this.status = DownloadStatus.Stopped;
            this.output = new ItemOutput(this);
            this.origin = origin;

            this.providers = new List<Guid>();
            this.sources = new List<ItemResource>();
            this.tasks = new Dictionary<ItemResource, IList<ITaskInfo>>();
        }
        #endregion

        #region Events
        public event EventHandler LocationChanged;
        public event EventHandler FolderChanged;
        public event EventHandler PriorityChanged;
        public event EventHandler StatusChanged;
        public event EventHandler SizeChanged;
        public event EventHandler ProgressChanged;

        public event EventHandler<ItemResourceEventArgs> SourceAdded;
        public event EventHandler<ItemResourceEventArgs> SourceChanged;
        public event EventHandler<ItemResourceEventArgs> SourceRemoved;
        #endregion
        #region Events' Raisers
        private void OnSizeChanged()
        {
            if (this.SizeChanged != null)
                this.SizeChanged(this, EventArgs.Empty);
        }

        private void OnSourceAdded(ItemResource source)
        {
            if (this.SourceAdded != null)
                this.SourceAdded(this, new ItemResourceEventArgs(source));
        }

        private void OnSourceChanged(ItemResource source)
        {
            if (this.SourceChanged != null)
                this.SourceChanged(this, new ItemResourceEventArgs(source));
        }

        private void OnSourceRemoved(ItemResource source)
        {
            if (this.SourceRemoved != null)
                this.SourceRemoved(this, new ItemResourceEventArgs(source));
        }
        #endregion

        #region Public Members
        public override ItemType Type
        {
            get { return ItemType.Download; }
        }

        public DateTime Stamp
        {
            get { return this.stamp; }
            set { this.stamp = value; }
        }

        public string Folder
        {
            get { return this.folder ?? String.Empty; }
            set { this.folder = value ?? String.Empty; }
        }

        public string Location
        {
            get { return this.location ?? String.Empty; }
            set { this.location = value ?? String.Empty; }
        }

        public ItemOutput Output
        {
            get { return this.output; }
        }

        public DataSize Size
        {
            get
            {
                var exacts = from source in this.sources
                             where source.Status != ResourceStatus.Incompatible
                             where source.Size.Mode == DataSizeMode.Exact
                             select source.Size;

                var sizes = from source in this.sources
                            where source.Status != ResourceStatus.Incompatible
                            select source.Size;

                var exact = exacts.FirstOrDefault();
                var average = DataSize.Average(sizes.ToArray());

                return exact ?? average ?? DataSize.Unknown;
            }
        }

        public long Progress
        {
            get { return this.progress; }
            set { this.progress = value; }
        }

        public DownloadPriority Priority
        {
            get { return this.priority; }
            set { this.priority = value; }
        }

        public DownloadStatus Status
        {
            get { return this.status; }
            set { this.status = value; }
        }

        public bool? IsProtected
        {
            get { return this.isProtected; }
            set { this.isProtected = value; }
        }

        public bool? IsSuspected
        {
            get { return this.isSuspected; }
            set { this.isSuspected = value; }
        }

        #region Providers
        public Guid[] GetProviders()
        {
            lock (this.providers)
                return this.providers.ToArray();
        }

        public void AddProvider(Guid provider)
        {
            lock (this.providers)
                this.providers.Add(provider);
        }

        public void RemoveProvider(Guid provider)
        {
            lock (this.providers)
                this.providers.Remove(provider);
        }

        public void ClearProviders()
        {
            lock (this.providers)
                this.providers.Clear();
        }
        #endregion
        #region Sources
        public ItemResource GetSource(Guid id)
        {
            lock (this.sources)
                return this.sources.FirstOrDefault(x => x.Id == id);
        }

        public ItemResource[] GetSources()
        {
            lock (this.sources)
                return this.sources.ToArray();
        }

        public bool ContainsSource(ItemResource source)
        {
            lock (this.sources)
                return this.sources.Contains(source);
        }

        public void AddSource(ItemResource source)
        {
            lock (this.sources)
            {
                if (this.sources.Any(x => x.Location == source.Location))
                    return;

                this.sources.Add(source);
                this.AttachEvents(source);
            }

            this.OnSourceAdded(source);
        }

        public void RemoveSource(ItemResource source)
        {
            lock (this.sources)
            {
                if (this.sources.Remove(source))
                {
                    this.DetachEvents(source);
                }

                this.OnSourceRemoved(source);
            }
        }
        #endregion
        #region Tasks
        public bool ContainsTask(ItemResource source)
        {
            lock (this.tasks)
                return this.tasks.ContainsKey(source);
        }

        public ITaskInfo[] GetTasks()
        {
            lock (this.tasks)
                return this.tasks.Values.SelectMany(x => x).ToArray();
        }
        
        public ITaskInfo[] GetTasks(ItemResource source)
        {
            lock (this.tasks)
                if (this.tasks.ContainsKey(source))
                    return this.tasks[source].ToArray();

            return new ITaskInfo[] { };
        }

        public void AddTask(ItemResource source, ITaskInfo task)
        {
            lock (this.tasks)
            {
                if (!this.tasks.ContainsKey(source))
                    this.tasks.Add(source, new List<ITaskInfo>());

                this.tasks[source].Add(task);
            }
        }

        public void RemoveTasks(ItemResource source)
        {
            lock (this.tasks)
                this.tasks.Remove(source);
        }

        public void RemoveTask(ITaskInfo task)
        {
            lock (this.tasks)
            {
                foreach (var entry in this.tasks.ToArray())
                {
                    foreach (ITaskInfo info in entry.Value.ToArray())
                        if (info.Equals(task))
                            entry.Value.Remove(task);

                    if (entry.Value.Count == 0)
                        this.tasks.Remove(entry.Key);
                }
            }
        }
        #endregion

        public ItemOrigin Origin
        {
            get { return this.origin; }
        }

        public bool IsDownloading()
        {
            return this.status == DownloadStatus.Downloading;
        }

        public bool IsWorking()
        {
            return this.status == DownloadStatus.Downloading || this.status == DownloadStatus.Queued;
        }
        #endregion
        #region Private Members
        private void AttachEvents(ItemResource source)
        {
            source.SizeChanged += this.OnSourceSizeChanged;
            source.StatusChanged += this.OnSourceStatusChanged;
        }

        private void DetachEvents(ItemResource source)
        {
            source.SizeChanged -= this.OnSourceSizeChanged;
            source.StatusChanged -= this.OnSourceStatusChanged;
        }

        private void OnSourceSizeChanged(object sender, EventArgs e)
        {
            this.OnSizeChanged();
        }

        private void OnSourceStatusChanged(object sender, EventArgs e)
        {
            this.OnSourceChanged((ItemResource)sender);
        }
        #endregion

        #region IClassifiable Members
        Guid IClassifiable.Id
        {
            get { return this.Id; }
        }

        string[] IClassifiable.GetKeywords()
        {
            var result = new List<string>();

            result.AddRange(this.origin.GetKeywords());
            result.AddRange(this.sources.SelectMany(x => x.Origin.GetKeywords()));

            return result.ToArray();
        }
        #endregion
    }
}
